<?php


namespace EchoPHP\Validator;

use Countable;
use EchoPHP\File\File;
use EchoPHP\File\UploadedFile;
use InvalidArgumentException;
use DateTime;

trait ValidateAttribute {

    protected static $typeMsg = [
        'required' => ':attribute不能为空',
        'int' => ':attribute必须是整数',
        'number' => ':attribute必须是数字',
        'float' => ':attribute必须是浮点数',
        'boolean' => ':attribute必须是布尔值',
        'array' => ':attribute必须是数组',
        'alpha' => ':attribute只能是字母',
        'alphaNum' => ':attribute只能是字母和数字',
        'alphaDash' => ':attribute只能是字母、数字和下划线_及破折号-',
        'hanzi' => ':attribute只能是汉字',
        'hanziAlpha' => ':attribute只能是汉字、字母',
        'hanziAlphaNum' => ':attribute只能是汉字、字母和数字',
        'hanziAlphaDash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-',
        'in' => ':attribute必须在 :rule 范围内',
        'notIn' => ':attribute不能在 :rule 范围内',
        'minLength' => ':attribute长度不能小于 :rule',
        'maxLength' => ':attribute长度不能大于 :rule',
        'min' => ':attribute不能小于 :rule',
        'max' => ':attribute不能大于 :rule',
        'equal' => ':attribute的值与 :rule不一致',
        'equalAttribute' => ':attribute的值与 :rule的值不一致',
        'notEqualAttribute' => ':attribute的值与 :rule的值不能相同',
        'dateFormat' => ':attribute必须使用日期格式 :rule',
        'before' => ':attribute必须早于 :rule',
        'beforeOrEqual' => ':attribute必须早于或等于 :rule',
        'after' => ':attribute必须晚于 :rule',
        'afterOrEqual' => ':attribute必须晚于或等于 :rule',
        'regex' => ':attribute 与正则 :rule 不匹配',
        'url' => ':attribute不是合法URL地址',
        'ip' => ':attribute 不是合法IP地址',
        'ipv4' => ':attribute不是合法的IPV4地址',
        'ipv6' => ':attribute不是合法的IPV6地址',
        'email' => ':attribute不是合法的邮箱地址',
        'tel' => ':attribute不是合法的固定电话号码',
        'phone' => ':attribute不是合法的移动手机电话',
        'telPhone' => ':attribute不是合法的固定电话或移动手机号码',
        'idCard' => ':attribute不会合法身份证号码',
        'file' => ':attribute 不是合法文件类型',
        'minSize' => '文件 :attribute最少 :rule KB',
        'maxSize' => '文件 :attribute最大 :rule KB',
        'mimes' => '文件 :attribute上传格式要求[:rule]',
    ];

    /**
     * 校验字段必填
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateRequired($attribute, $value) {
        if (is_null($value)) {
            return false;
        } elseif (is_string($value) && trim($value) === '') {
            return false;
        } elseif ((is_array($value) || $value instanceof Countable) && count($value) < 1) {
            return false;
        } elseif ($value instanceof File) {
            return (string)$value->getPath() !== '';
        }

        return true;
    }

    /**
     * 验证的字段必须是整数
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateInt($attribute, $value) {
        return filter_var($value, FILTER_VALIDATE_INT) !== false;
    }

    /**
     * 验证的字段必须是数字
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateNumber($attribute, $value) {
        return is_numeric($value);
    }

    /**
     * 验证的字段必须是浮点数
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateFloat($attribute, $value) {
        return filter_var($value, FILTER_VALIDATE_FLOAT) !== false;
    }

    /**
     * 验证字段必须是 真或假
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateBoolean($attribute, $value) {
        return in_array($value, [true, false, 0, 1, '0', '1'], true);
    }

    /**
     * 验证的字段必须是数组
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateArray($attribute, $value) {
        return is_array($value);
    }

    /**
     * 验证字段必须完全由字母构成
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateAlpha($attribute, $value) {
        return is_string($value) && preg_match('/^[A-Za-z]+$/u', $value);
    }

    /**
     * 验证字段必须是完全是字母、数字。
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateAlphaNum($attribute, $value) {
        if (!is_string($value) && !is_numeric($value)) {
            return false;
        }

        return preg_match('/^[A-Za-z0-9]+$/u', $value) > 0;
    }

    /**
     * 验证字段可能包含字母、数字，以及破折号 (-) 和下划线 ( _ )。
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateAlphaDash($attribute, $value) {
        if (!is_string($value) && !is_numeric($value)) {
            return false;
        }

        return preg_match('/^[A-Za-z0-9\-\_]+$/u', $value) > 0;
    }

    /**
     * 验证字段必须完全由汉字构成
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateHanzi($attribute, $value) {
        return is_string($value) && preg_match('/^[\x{4e00}-\x{9fa5}]+$/u', $value);
    }

    /**
     * 验证字段必须完全由汉字和字母构成
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateHanziAlpha($attribute, $value) {
        return is_string($value) && preg_match('/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', $value);
    }

    /**
     * 验证字段必须完全由汉字和字母或数字构成
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateHanziAlphaNum($attribute, $value) {
        return is_string($value) && preg_match('/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u', $value);
    }

    /**
     * 验证字段可能包含汉字、字母、数字，以及破折号 (-) 和下划线 ( _ )。
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateHanziAlphaDash($attribute, $value) {
        return is_string($value) && preg_match('/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u', $value);
    }

    /**
     * 验证是否在范围内
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateIn($attribute, $value, $parameters) {
        return in_array($value, is_array($parameters) ? $parameters : explode(',', $parameters));
    }

    /**
     * 验证是否不在某个范围
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateNotIn($attribute, $value, $parameters) {
        return !in_array($value, is_array($parameters) ? $parameters : explode(',', $parameters));
    }

    /**
     * 验证字符串最小长度
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateMinLength($attribute, $value, $parameters) {
        if (!$this->validateInt($attribute, $parameters) || $parameters < 0) {
            throw new InvalidArgumentException('验证规则[minLength]需要传入非负整数');
        }
        $length = mb_strlen((string)$value);
        return $length >= $parameters;
    }

    /**
     * 验证字符串最大长度
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateMaxLength($attribute, $value, $parameters) {
        if (!$this->validateInt($attribute, $parameters) || $parameters < 0) {
            throw new InvalidArgumentException('验证规则[maxLength]需要传入正整数');
        }
        $length = mb_strlen((string)$value);
        return $length <= $parameters;
    }

    /**
     * 验证数值最小值
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateMin($attribute, $value, $parameters) {
        if (!is_numeric($parameters)) {
            throw new InvalidArgumentException('验证规则[min]需要传入数字');
        }
        return is_numeric($value) && $value >= $parameters;
    }

    /**
     * 验证数值最大值
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateMax($attribute, $value, $parameters) {
        if (!is_numeric($parameters)) {
            throw new InvalidArgumentException('验证规则[max]需要传入数字');
        }
        return is_numeric($value) && $value <= $parameters;
    }

    /**
     * 验证是否http上传文件
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateFile($attribute, $value) {
        return $this->isValidFileInstance($value);
    }

    /**
     * 判断是文件实例
     * @param $value
     * @return bool
     */
    public function isValidFileInstance($value) {
        if ($value instanceof UploadedFile && !$value->isValid()) {
            return false;
        }
        return $value instanceof File;
    }

    /**
     * 获取最小文件大小
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateMinSize($attribute, $value, $parameters) {
        if (!$value instanceof File) {
            return false;
        }
        $fileSize = $value->getSize() / 1024;
        return is_numeric($parameters) && $fileSize >= $parameters;
    }

    /**
     * 获取最大文件大小
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateMaxSize($attribute, $value, $parameters) {
        if (!$value instanceof File) {
            return false;
        }
        $fileSize = $value->getSize() / 1024;
        return is_numeric($parameters) && $fileSize <= $parameters;
    }

    /**
     * @param $attribute
     * @param UploadedFile $value
     * @param $parameters
     * @return bool
     */
    public function validateMimes($attribute, $value, $parameters) {
        if (!$this->isValidFileInstance($value)) {
            return false;
        }

        if (is_string($parameters)) {
            $parameters = explode(',', $parameters);
        }

        return $value->getPath() !== '' && in_array($value->guessExtension(), $parameters);
    }

    /**
     * 比较相同
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateEqual($attribute, $value, $parameters) {
        return $value == $parameters;
    }

    /**
     * 比较另一个字段的值
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateEqualAttribute($attribute, $value, $parameters) {
        $attributeValue = $this->getData((string)$parameters);
        return $value == $attributeValue;
    }

    /**
     * 验证不相同
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateNotEqualAttribute($attribute, $value, $parameters) {
        $attributeValue = $this->getData((string)$parameters);
        return $value == $attributeValue ? false : true;
    }

    /**
     * 校验日期格式
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateDateFormat($attribute, $value, $parameters) {
        if (!is_string($value) && !is_numeric($value)) {
            return false;
        }
        $date = DateTime::createFromFormat('!' . $parameters, $value);

        return $date && $date->format($parameters) == $value;
    }

    /**
     * 验证字段必须是在给定日期之前
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateBefore($attribute, $value, $parameters) {
        return $this->compareDates($attribute, $value, $parameters, '<');
    }

    /**
     * 验证字段必须是在给定日期之前或与之相同的日期
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateBeforeOrEqual($attribute, $value, $parameters) {
        return $this->compareDates($attribute, $value, $parameters, '<=');
    }

    /**
     * 验证字段必须是在给定日期之后
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateAfter($attribute, $value, $parameters) {
        return $this->compareDates($attribute, $value, $parameters, '>');
    }

    /**
     * 验证字段必须是在给定日期之后或与此日期相同的值
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateAfterOrEqual($attribute, $value, $parameters) {
        return $this->compareDates($attribute, $value, $parameters, '>=');
    }

    /**
     * 时间日期比较
     * @param $attribute
     * @param $value
     * @param $parameters
     * @param $operator
     * @return bool
     */
    protected function compareDates($attribute, $value, $parameters, $operator) {
        if (is_numeric($value)) {
            $firstTimeStamp = $value;
        } elseif (is_string($value)) {
            $firstTimeStamp = strtotime($value);
        } else {
            return false;
        }
        if (is_numeric($parameters)) {
            $secondTimeStamp = $parameters;
        } else {
            if (!$secondTimeStamp = strtotime($parameters)) {
                $secondTimeStamp = strtotime($this->getData($parameters));
            }
        }
        return $this->compare($firstTimeStamp, $secondTimeStamp, $operator);
    }

    /**
     * 比较两个值
     * @param $first
     * @param $second
     * @param $operator
     * @return bool
     */
    protected function compare($first, $second, $operator) {
        switch ($operator) {
            case '<':
                return $first < $second;
            case '>':
                return $first > $second;
            case '<=':
                return $first <= $second;
            case '>=':
                return $first >= $second;
            case '=':
                return $first == $second;
            default:
                throw new InvalidArgumentException;
        }
    }

    /**
     * 验证正则
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateRegex($attribute, $value, $parameters) {
        if (!is_string($value) && !is_numeric($value)) {
            return false;
        }

        return preg_match($parameters, $value) > 0;
    }

    /**
     * 判断是不是URL
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return bool
     */
    public function validateUrl($attribute, $value) {
        if (!is_string($value)) {
            return false;
        }

        /*
         * This pattern is derived from Symfony\Component\Validator\Constraints\UrlValidator (2.7.4).
         *
         * (c) Fabien Potencier <fabien@symfony.com> http://symfony.com
         */
        $pattern = '~^
            ((aaa|aaas|about|acap|acct|acr|adiumxtra|afp|afs|aim|apt|attachment|aw|barion|beshare|bitcoin|blob|bolo|callto|cap|chrome|chrome-extension|cid|coap|coaps|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-playcontainer|dlna-playsingle|dns|dntp|dtn|dvb|ed2k|example|facetime|fax|feed|feedready|file|filesystem|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|ham|hcp|http|https|iax|icap|icon|im|imap|info|iotdisco|ipn|ipp|ipps|irc|irc6|ircs|iris|iris.beep|iris.lwz|iris.xpc|iris.xpcs|itms|jabber|jar|jms|keyparc|lastfm|ldap|ldaps|magnet|mailserver|mailto|maps|market|message|mid|mms|modem|ms-help|ms-settings|ms-settings-airplanemode|ms-settings-bluetooth|ms-settings-camera|ms-settings-cellular|ms-settings-cloudstorage|ms-settings-emailandaccounts|ms-settings-language|ms-settings-location|ms-settings-lock|ms-settings-nfctransactions|ms-settings-notifications|ms-settings-power|ms-settings-privacy|ms-settings-proximity|ms-settings-screenrotation|ms-settings-wifi|ms-settings-workplace|msnim|msrp|msrps|mtqp|mumble|mupdate|mvn|news|nfs|ni|nih|nntp|notes|oid|opaquelocktoken|pack|palm|paparazzi|pkcs11|platform|pop|pres|prospero|proxy|psyc|query|redis|rediss|reload|res|resource|rmi|rsync|rtmfp|rtmp|rtsp|rtsps|rtspu|secondlife|s3|service|session|sftp|sgn|shttp|sieve|sip|sips|skype|smb|sms|smtp|snews|snmp|soap.beep|soap.beeps|soldat|spotify|ssh|steam|stun|stuns|submit|svn|tag|teamspeak|tel|teliaeid|telnet|tftp|things|thismessage|tip|tn3270|turn|turns|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|videotex|view-source|wais|webcal|ws|wss|wtai|wyciwyg|xcon|xcon-userid|xfire|xmlrpc\.beep|xmlrpc.beeps|xmpp|xri|ymsgr|z39\.50|z39\.50r|z39\.50s))://                                 # protocol
            (([\pL\pN-]+:)?([\pL\pN-]+)@)?          # basic auth
            (
                ([\pL\pN\pS\-\.])+(\.?([\pL]|xn\-\-[\pL\pN-]+)+\.?) # a domain name
                    |                                              # or
                \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}                 # an IP address
                    |                                              # or
                \[
                    (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::))))
                \]  # an IPv6 address
            )
            (:[0-9]+)?                              # a port (optional)
            (/?|/\S+|\?\S*|\#\S*)                   # a /, nothing, a / with something, a query or a fragment
        $~ixu';

        return preg_match($pattern, $value) > 0;
    }

    /**
     * 验证IP
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateIp($attribute, $value) {
        return filter_var($value, FILTER_VALIDATE_IP) !== false;
    }

    /**
     * 验证IPv4
     *
     * @param string $attribute
     * @param mixed $value
     * @return bool
     */
    public function validateIpv4($attribute, $value) {
        return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
    }

    /**
     * 验证IPv6
     *
     * @param string $attribute
     * @param mixed $value
     * @return bool
     */
    public function validateIpv6($attribute, $value) {
        return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
    }

    /**
     * 邮箱校验
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateEmail($attribute, $value) {
        if (!is_string($value)) {
            return false;
        }

        return preg_match('/^(([0-9a-zA-Z]+)|([0-9a-zA-Z]+[_.0-9a-zA-Z-]*))@([a-zA-Z0-9-]+[.])+([a-zA-Z]{2,5})$/', $value) > 0;
    }

    /**
     * 校验座机
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateTel($attribute, $value) {
        if (!is_string($value)) {
            return false;
        }
        $pattern = '/(^((\d{3,4})-?)?(\d{6,8})(-?(\d{1,6}))?$)/';

        return preg_match($pattern, $value) > 0;
    }

    /**
     * 校验手机
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validatePhone($attribute, $value) {
        if (!is_string($value)) {
            return false;
        }
        $pattern = '/(^(([+]{0,1}\d{2,4}|\d{2,4})-?)?1[3456789]\d{9}$)/';

        return preg_match($pattern, $value) > 0;
    }

    /**
     * 验证联系方式 手机或座机
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateTelPhone($attribute, $value) {
        if (!is_string($value)) {
            return false;
        }
        $telPattern = '((\d{3,4})-?)?(\d{6,8})(-?(\d{1,6}))?';
        $phonePattern = '(([+]{0,1}\d{2,4}|\d{2,4})-?)?1[3456789]\d{9}';
        $pattern = '/(^((' . ($telPattern) . ')|(' . $phonePattern . '))$)/';

        return preg_match($pattern, $value) > 0;
    }

    /**
     * 身份证号码校验
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function validateIdCard($attribute, $value) {
        if (!is_string($value)) {
            return false;
        }
        $pattern = '/^\d{15}$|^\d{17}[\dXx]$/';

        return preg_match($pattern, $value) > 0;
    }
}